﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using MugenInjection.Delegates;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Scope;

namespace MugenInjection.Bindings.Builders
{
    /// <summary>
    /// Represent the binding builder setting keys.
    /// </summary>
    public class BindingBuilderConfigurator
    {
        #region Nested type: Configure

        private sealed class Configure : IDisposable
        {
            #region Fields

            private readonly Action _action;

            #endregion

            #region Constructors

            /// <summary>
            /// Initializes a new instance of the <see cref="Configure"/> class.
            /// </summary>
            public Configure(Action action)
            {
                _action = action;
            }

            #endregion

            #region Implementation of IDisposable

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
                _action();
            }

            #endregion
        }

        #endregion

        #region Nested type: BindingType

        internal enum BindingType
        {
            Constant,
            Method,
            CustomBinding,
            Type
        }

        #endregion

        #region Binding builder values

        public static readonly string AddBindingToInjectorAfterBuildKey = "_AutoAddToInjector_";

        public static readonly string ActivatingActionKey = "_OnActivatingKey_";

        public static readonly string ActivatedActionKey = "_OnActivatedKey_";

        public static readonly string DisposedActionKey = "_OnDisposedKey_";

        public static readonly string ReleaseObjectActionKey = "_ReleaseObjectActionKey_";

        public static readonly string TryDisposeObjectKey = "_TryDisposeKey_";

        public static readonly string IsConfiguringKey = "_IsConfiguringKey_";

        public static readonly string PriorityBindingKey = "_PriorityKey_";

        public static readonly string BindingSettingsKey = "_SettingsKey_";

        public static readonly string LifecycleScopeFuncKey = "_LifeCycleKey_";

        public static readonly string CanResolveBindingConditionKey = "_CanResolveKey_";

        public static readonly string BindingParametersKey = "_ParametersKey_";

        public static readonly string KeyedBindingKey = "_KeyedKey_";

        public static readonly string ActivatorFuncKey = "_ActivatorKey_";

        public static readonly string ConstBindingValueKey = "_ConstValueKey_";

        public static readonly string CustomBindingValueKey = "_CustomValueKey_";

        public static readonly string MethodValueKey = "_MethodKey_";

        public static readonly string TypeToValueKey = "_TypeToKey_";

        public static readonly string ConstructorKey = "_ConstructorKey_";

        public static readonly string ConstructorResolverFuncKey = "_ConstructorResolverKey_";

        //        public static readonly string BuilderKey = "____BaseBuilderKey____";

        #endregion

        #region Fields

        private readonly Configure _configure;

        private readonly object _locker = new object();
        private readonly ISettings _settings;
        private bool _setPriority;
        private readonly List<BindingBuildingDelegate> _bindingBuildingDelegates;
        private readonly List<BindingBuildedDelegate> _bindingBuildedDelegates;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="BindingBuilderConfigurator"/> class.
        /// </summary>
        internal BindingBuilderConfigurator()
        {
            _bindingBuildedDelegates = new List<BindingBuildedDelegate>();
            _bindingBuildingDelegates = new List<BindingBuildingDelegate>();
            _settings = new DictionarySettingsImpl();
            Priority = BindingPriority.Standard;
            IsConfiguring = false;
            _configure = new Configure(() => IsConfiguring = false);
            BindingSettings = new Dictionary<string, object>();
            InjectionParameters = new List<IInjectionParameter>();
            ActivatingActions = new List<Action<IBindingContext>>();
            ActivatedActions = new List<BindingActivatedDelegate<object>>();
            DisposedActions = new List<Action<IBinding>>();
            ReleaseObjectActions = new List<Action<object, bool>>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BindingBuilderConfigurator"/> class.
        /// </summary>
        internal BindingBuilderConfigurator(IBindingBuilder builder, bool addBindingToInjector)
        {
            Validate.ArgumentNotNull(builder, "builder");
            _bindingBuildedDelegates = new List<BindingBuildedDelegate>();
            _bindingBuildingDelegates = new List<BindingBuildingDelegate>();
            _settings = new DictionarySettingsImpl();
            AddBindingToInjector = addBindingToInjector;
            BaseBuilder = builder;
            Priority = BindingPriority.Standard;
            IsConfiguring = false;
            _configure = new Configure(() => IsConfiguring = false);
            BindingSettings = new Dictionary<string, object>();
            InjectionParameters = new List<IInjectionParameter>();
            ActivatingActions = new List<Action<IBindingContext>>();
            ActivatedActions = new List<BindingActivatedDelegate<object>>();
            DisposedActions = new List<Action<IBinding>>();
            ReleaseObjectActions = new List<Action<object, bool>>();
        }

        #endregion

        #region Properties

        internal ISettings Settings
        {
            get { return _settings; }
        }

        private IBindingBuilder BaseBuilder { get; set; }

        internal BindingType? BuildBindingType { get; set; }

        internal object ConstValue
        {
            get { return _settings.Get(ConstBindingValueKey); }
            set { _settings.Add(ConstBindingValueKey, value); }
        }

        internal IBinding CustomBindingValue
        {
            get { return _settings.Get<IBinding>(CustomBindingValueKey); }
            set { _settings.Add(CustomBindingValueKey, value); }
        }

        internal MethodBindingDelegate MethodValue
        {
            get { return _settings.Get<MethodBindingDelegate>(MethodValueKey); }
            set { _settings.Add(MethodValueKey, value); }
        }

        internal Type TypeToValue
        {
            get { return _settings.Get<Type>(TypeToValueKey); }
            set { _settings.Add(TypeToValueKey, value); }
        }

        internal Func<IConstructorResolver> ConstructorResolverFunc
        {
            get { return SafeGetSetting<Func<IConstructorResolver>>(ConstructorResolverFuncKey); }
            set { _settings[ConstructorResolverFuncKey] = value; }
        }

        internal ConstructorInfo Constructor
        {
            get { return SafeGetSetting<ConstructorInfo>(ConstructorKey); }
            set { _settings[ConstructorKey] = value; }
        }

        internal List<IInjectionParameter> InjectionParameters
        {
            get { return _settings.Get<List<IInjectionParameter>>(BindingParametersKey); }
            private set { _settings.Add(BindingParametersKey, value); }
        }

        internal bool IsConfiguring
        {
            get { return _settings.Get<bool>(IsConfiguringKey); }
            set { _settings[IsConfiguringKey] = value; }
        }

        internal IDisposable BeginConfigure
        {
            get
            {
                IsConfiguring = true;
                return _configure;
            }
        }

        private bool AddBindingToInjector
        {
            get { return _settings.Get<bool>(AddBindingToInjectorAfterBuildKey); }
            set { _settings.Add(AddBindingToInjectorAfterBuildKey, value); }
        }

        private IList<Action<IBindingContext>> ActivatingActions
        {
            get { return _settings.Get<IList<Action<IBindingContext>>>(ActivatingActionKey); }
            set { _settings.Add(ActivatingActionKey, value); }
        }

        private IList<BindingActivatedDelegate<object>> ActivatedActions
        {
            get { return _settings.Get<IList<BindingActivatedDelegate<object>>>(ActivatedActionKey); }
            set { _settings.Add(ActivatedActionKey, value); }
        }

        private IList<Action<IBinding>> DisposedActions
        {
            get { return _settings.Get<IList<Action<IBinding>>>(DisposedActionKey); }
            set { _settings.Add(DisposedActionKey, value); }
        }

        private IList<Action<object, bool>> ReleaseObjectActions
        {
            get { return _settings.Get<IList<Action<object, bool>>>(ReleaseObjectActionKey); }
            set { _settings.Add(ReleaseObjectActionKey, value); }
        }

        private object Keyed
        {
            get { return SafeGetSetting<object>(KeyedBindingKey); }
            set { _settings.Add(KeyedBindingKey, value); }
        }

        private Dictionary<string, object> BindingSettings
        {
            get { return _settings.Get<Dictionary<string, object>>(BindingSettingsKey); }
            set { _settings.Add(BindingSettingsKey, value); }
        }

        private bool TryDispose
        {
            get { return SafeGetSetting<bool>(TryDisposeObjectKey); }
            set { _settings.Add(TryDisposeObjectKey, value); }
        }

        private CanResolveBindingDelegate CanResolveBindingValue
        {
            get { return SafeGetSetting<CanResolveBindingDelegate>(CanResolveBindingConditionKey); }
            set { _settings.Add(CanResolveBindingConditionKey, value); }
        }


        private BindingPriority Priority
        {
            get { return _settings.Get<BindingPriority>(PriorityBindingKey); }
            set { _settings[PriorityBindingKey] = value; }
        }

        private BindingPriority BindingPriority
        {
            get { return Priority; }
            set
            {
                Priority = value;
                _setPriority = true;
            }
        }

        private Func<IScopeLifecycle> DependencyLifecycleFunc
        {
            get { return SafeGetSetting<Func<IScopeLifecycle>>(LifecycleScopeFuncKey); }
            set { _settings.Add(LifecycleScopeFuncKey, value); }
        }

        private Func<IActivator> ActivatorFunc
        {
            get { return SafeGetSetting<Func<IActivator>>(ActivatorFuncKey); }
            set { _settings[ActivatorFuncKey] = value; }
        }

        #endregion

        #region Methods

        internal IBinding Build()
        {
            var bindingBuilder = BaseBuilder;
            lock (_locker)
            {
                foreach (var bindingBuildingDelegate in _bindingBuildingDelegates)
                {
                    bindingBuilder = bindingBuildingDelegate(bindingBuilder);
                    Validate.FactoryReturnNotNullValue(bindingBuilder, "IBindingBuilder");
                }

                IBinding binding = null;
                if (_bindingBuildedDelegates.Count == 0)
                    binding = bindingBuilder.Build();
                else
                {
                    foreach (var bindingBuildedDelegate in _bindingBuildedDelegates)
                    {
                        binding = bindingBuildedDelegate(bindingBuilder);
                    }
                }
                Validate.FactoryReturnNotNullValue(binding, "IBinding");
                if (AddBindingToInjector)
                    bindingBuilder.Injector.AddBinding(binding);
                return binding;
            }
        }

        protected T SafeGetSetting<T>(string name)
        {
            T result;
            _settings.TryGet(name, out result);
            return result;
        }

        internal void AddBuildedEvent(BindingBuildedDelegate bindingBuildedDelegate)
        {
            Validate.ArgumentNotNull(bindingBuildedDelegate, "bindingBuildedDelegate");
            lock (_locker)
            {
                _bindingBuildedDelegates.Add(bindingBuildedDelegate);
            }
        }

        internal void AddBuildingEvent(BindingBuildingDelegate bindingBuildingDelegate)
        {
            Validate.ArgumentNotNull(bindingBuildingDelegate, "bindingBuildingDelegate");
            lock (_locker)
            {
                _bindingBuildingDelegates.Add(bindingBuildingDelegate);
            }
        }

        internal void RemoveBuildedEvent(BindingBuildedDelegate bindingBuildedDelegate)
        {
            Validate.ArgumentNotNull(bindingBuildedDelegate, "bindingBuildedDelegate");
            lock (_locker)
            {
                _bindingBuildedDelegates.Remove(bindingBuildedDelegate);
            }
        }

        internal void RemoveBuildingEvent(BindingBuildingDelegate bindingBuildingDelegate)
        {
            Validate.ArgumentNotNull(bindingBuildingDelegate, "bindingBuildingDelegate");
            lock (_locker)
            {
                _bindingBuildingDelegates.Remove(bindingBuildingDelegate);
            }
        }

        internal IScopeLifecycle GetDependencyLifecycle()
        {
            var dependencyLifecycleFunc = DependencyLifecycleFunc;
            if (dependencyLifecycleFunc == null)
                dependencyLifecycleFunc = BaseBuilder.Injector.Settings.DefaultScopeLifecycleFactory;
            var scopeLifecycleBase = dependencyLifecycleFunc();
            Validate.FactoryReturnNotNullValue(scopeLifecycleBase, "ScopeLifecycle");
            return scopeLifecycleBase;
        }

        internal CanResolveBindingDelegate GetCanResolveCondition()
        {
            var keyed = Keyed;
            bool isNotKeyed = keyed == null;
            var canResolveBindingDelegate = CanResolveBindingValue;
            if (isNotKeyed)
            {
                if (CanResolveBindingValue == null)
                    return NotKeyedBinding;

                return binding => CombinateCanResolve(binding, NotKeyedBinding, canResolveBindingDelegate);
            }

            if (canResolveBindingDelegate == null)
                return binding => CanResolveKeyedBinding(binding, keyed);
            return binding => canResolveBindingDelegate(binding) && CanResolveKeyedBinding(binding, keyed);
        }

        internal void AddOptionalSetting(BindingBase bindingBase)
        {
            foreach (var activating in ActivatingActions)
                bindingBase.ActivatingActions.Add(activating);
            foreach (var activated in ActivatedActions)
                bindingBase.ActivatedActions.Add(activated);
            foreach (var disposed in DisposedActions)
                bindingBase.DeactivatedActions.Add(disposed);

            if (bindingBase.ScopeLifecycle != null && bindingBase.ScopeLifecycle.ReleaseObjectActions != null &&
                !bindingBase.ScopeLifecycle.ReleaseObjectActions.IsReadOnly)
                foreach (var releaseObjectAction in ReleaseObjectActions)
                    bindingBase.ScopeLifecycle.ReleaseObjectActions.Add(releaseObjectAction);
            bindingBase.TryDisposeObject = TryDispose;
            bindingBase.Priority = BindingPriority;
            foreach (var o in BindingSettings)
                bindingBase.Settings.Add(o.Key, o.Value);
        }

        private void CheckScope()
        {
            if (DependencyLifecycleFunc != null)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("scope");
        }

        internal void SetScope(Func<IScopeLifecycle> dependencyLifecycle)
        {
            Validate.ArgumentNotNull(dependencyLifecycle, "dependencyLifecycle");
            CheckScope();
            DependencyLifecycleFunc = dependencyLifecycle;
        }

        internal void SetScope(CustomScopeDelegate customScopeDelegate)
        {
            if (customScopeDelegate == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("customScopeDelegate",
                                                                                "customScopeDelegate");
            SetScope(() => new CustomScopeLifecycle(customScopeDelegate));
        }

        internal void SetOnActivating(Action<IBindingContext> onActivating)
        {
            if (onActivating == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("onActivating callback",
                                                                                "onActivating");
            ActivatingActions.Add(onActivating);
        }

        internal void SetOnActivated(BindingActivatedDelegate<object> onActivated)
        {
            if (onActivated == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("onActivated callback",
                                                                                "onActivated");
            ActivatedActions.Add(onActivated);
        }

        internal void SetOnDisposed(Action<IBinding> onDisposed)
        {
            if (onDisposed == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("onDisposed callback",
                                                                                "onDisposed");
            DisposedActions.Add(onDisposed);
        }

        internal void SetOnReleaseObject(Action<object, bool> onReleaseObject)
        {
            if (onReleaseObject == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("onReleaseObject callback",
                                                                                "onReleaseObject");
            ReleaseObjectActions.Add(onReleaseObject);
        }

        internal void SetPriorityBase(BindingPriority priority)
        {
            if (_setPriority)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("binding priority");
            BindingPriority = priority;
        }

        private void SetCanResolve(CanResolveBindingDelegate canResolveBindingDelegate,
                                   BindingPriority bindingPriority)
        {
            if (canResolveBindingDelegate == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("canResolveBindingDelegate",
                                                                                "canResolveBindingDelegate");

            if (CanResolveBindingValue != null)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("when condition");
            Priority = bindingPriority;
            CanResolveBindingValue = canResolveBindingDelegate;
        }

        internal void SetCanResolve(CanResolveBindingDelegate canResolveBindingDelegate)
        {
            Validate.ArgumentNotNull(canResolveBindingDelegate, "canResolveBindingDelegate");
            SetCanResolve(canResolveBindingDelegate, BindingPriority.Normal);
            AddBindingSetting(SpecialParameterKeys.NoCacheBindingKey, true);
        }

        internal void SetWhenCanResolveNamespace(string nameSpace)
        {
            if (String.IsNullOrEmpty(nameSpace))
                throw new InvalidBindingException("The namespace is null or empty.",
                                                  new ArgumentNullException("nameSpace"));
            SetCanResolve(binding => CanResolveNamespace(binding, nameSpace), BindingPriority.Low);
        }

        internal void SetWhenClassHasAttribute(Type attributeType)
        {
            Validate.ArgumentNotNull(attributeType, "attributeType");
#if NETFX_CORE
            if (!typeof(Attribute).GetTypeInfo().IsAssignableFrom(attributeType.GetTypeInfo()))
#else
            if (!typeof(Attribute).IsAssignableFrom(attributeType))
#endif
                throw new InvalidBindingException("The attribute type is not valid.");
            SetCanResolve(binding => WhenClassHasAttribute(binding, attributeType), BindingPriority.Low);
        }

        internal void SetWhenCanResolveType(Type type)
        {
            if (type == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("type for condition", "type");
            SetCanResolve(binding => WhenCanResolveType(binding, type), BindingPriority.Low);
        }

        internal void SetWhenCanResolveTypeAssignable(Type type)
        {
            if (type == null)
                throw InvalidBindingException.CreateNullInvalidBindingException("type for condition", "type");
            SetCanResolve(binding => WhenCanResolveTypeAssignable(binding, type), BindingPriority.Low);
        }

        internal void AddParameter(IInjectionParameter parameter)
        {
            Validate.ArgumentNotNull(parameter, "parameter");
            foreach (IInjectionParameter injectionParameter in InjectionParameters)
            {
                if (injectionParameter.Equals(parameter))
                    throw InvalidBindingException.CreateDuplicateInvalidBindingException("parameter");
            }
            InjectionParameters.Add(parameter);
        }

        internal void SetTryDisposeObject()
        {
            if (TryDispose)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("TryDisposeObject");
            TryDispose = true;
        }

        internal void SetKeyedBinding(object key)
        {
            if (Keyed != null)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("keyed binding");
            if (key == null)
                throw new InvalidBindingException("The named binding cannot be null or empty name.");
            Keyed = key;
            Priority = BindingPriority.Normal;
        }

        internal void AddBindingSetting(string key, object value)
        {
            BindingSettings.Add(key, value);
        }

        internal void CheckToBinding()
        {
            if (BuildBindingType != null)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("bound type");
        }

        internal void CheckToBinding(Type typeTo)
        {
            CheckToBinding();
            foreach (Type service in BaseBuilder.Services)
            {
                if (!InjectorUtilsInternal.IsBindable(service, typeTo))
                    throw new InvalidBindingException(String.Format("The service {0} is not bindable to type {1}.",
                                                                    InjectorUtilsInternal.FormatType(service),
                                                                    InjectorUtilsInternal.FormatType(typeTo)));
            }
        }

        internal void CheckToBinding(object instance)
        {
            CheckToBinding();
            Type type = instance.GetType();
            foreach (Type service in BaseBuilder.Services)
            {
                Validate.IsAssignable(service, type);
            }
        }

        private void CheckActivator()
        {
            if (ActivatorFunc != null)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("activator");
        }

        internal void CheckConstructor()
        {
            if (Constructor != null)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("constructor");
            if (TypeToValue == null)
                throw new InvalidBindingException("It is impossible set constructor, for not bound type.");
        }

        internal void CheckConstructorResolver()
        {
            if (ConstructorResolverFunc != null)
                throw InvalidBindingException.CreateDuplicateInvalidBindingException("constructorResolver");
        }

        internal IActivator GetActivator(bool canBeNull)
        {
            Func<IActivator> activatorFunc = ActivatorFunc;
            if (activatorFunc == null)
                if (canBeNull)
                    return null;
                else
                    activatorFunc = BaseBuilder.Injector.Settings.DefaultActivatorFactory;
            IActivator activator = activatorFunc();
            Validate.FactoryReturnNotNullValue(activator, "IActivator");
            return activator;
        }

        internal void SetActivator(Func<IActivator> methodActivator)
        {
            Validate.ArgumentNotNull(methodActivator, "methodActivator");
            CheckActivator();
            ActivatorFunc = methodActivator;
        }

        internal IConstructorResolver GetResolver()
        {
            Func<IConstructorResolver> constructorResolverFunc = ConstructorResolverFunc;
            if (constructorResolverFunc == null)
                constructorResolverFunc = BaseBuilder.Injector.Settings.DefaultConstructorResolverFactory;
            IConstructorResolver resolver = constructorResolverFunc();
            Validate.FactoryReturnNotNullValue(resolver, "IConstructorResolver");
            ConstructorInfo constructor = Constructor;
            if (constructor != null)
                resolver.SetConstructor(constructor);
            return resolver;
        }

        #endregion

        #region Can resolve methods

        internal static bool CanResolveKeyedBinding(IBindingContext bindingContext, object key)
        {
            if (bindingContext.SpecialParameters == null) return false;
            object value;
            if (!bindingContext.SpecialParameters.TryGetValue(SpecialParameterKeys.KeyedBindingParameter, out value))
                return false;
            if (value == null) return false;
            return value.Equals(key);
        }

        internal static bool CanResolveNamespace(IBindingContext bindingContext, string @namespace)
        {
            if (bindingContext.TypeInto == null) return false;
            return String.Equals(bindingContext.TypeInto.Namespace, @namespace);
        }

        internal static bool WhenClassHasAttribute(IBindingContext bindingContext, Type attributeType)
        {
            if (bindingContext.TypeInto == null)
                return false;
#if NETFX_CORE
            return bindingContext.TypeInto.GetTypeInfo().IsDefined(attributeType, true);
#else
            return bindingContext.TypeInto.IsDefined(attributeType, true);
#endif
        }

        internal static bool WhenCanResolveType(IBindingContext bindingContext, Type type)
        {
            if (bindingContext.TypeInto == null)
                return false;
            return bindingContext.TypeInto == type;
        }

        internal static bool WhenCanResolveTypeAssignable(IBindingContext bindingContext, Type type)
        {
            if (bindingContext.TypeInto == null)
                return false;
#if NETFX_CORE
            return type.GetTypeInfo().IsAssignableFrom(bindingContext.TypeInto.GetTypeInfo());
#else
            return type.IsAssignableFrom(bindingContext.TypeInto);
#endif
        }

        internal static bool NotKeyedBinding(IBindingContext bindingContext)
        {
            return ReferenceEquals(bindingContext.SpecialParameters, InjectorUtilsInternal.ReadOnlyEmptyDictionary) ||
                   !bindingContext.SpecialParameters.ContainsKey(SpecialParameterKeys.KeyedBindingParameter);
        }

        internal static bool CombinateCanResolve(IBindingContext bindingContext,
                                                CanResolveBindingDelegate canResolveDelegate,
                                                CanResolveBindingDelegate canResolveBindingDelegate)
        {
            return canResolveDelegate(bindingContext) && canResolveBindingDelegate(bindingContext);
        }

        #endregion
    }
}